/*
 * Copyright (C) 2012 by Hao Hou<ghost89413@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>*
 */

#include <ic.h>
#include <string.h>
#include <X11/Xutil.h>
#include <init.h>
#include <xim.h>
#define STREQU(a,b) (strcmp((a),(b)) == 0)
#define CHK_CREATOR(ic,ret) do{if((ic)->CreatorThread != pthread_self()){VINE_LOG_ERROR("The function can not be invoked from thread other than ceator"); return ret;}} while(0);
InputContext* current_input_context = NULL;
InputContext IC_Buffer[MAX_IC_NUM] = {};
static CARD16 __ic_counter__ = 0;
typedef struct{
    int		major_code;
    int		minor_code;
    CARD16	connect_id;
    CARD16	icid;
} ICID;
InputContext* ICNew()
{
	int i;
	static pthread_mutex_t mutex_init = PTHREAD_MUTEX_INITIALIZER;
	for(i = 0; i < MAX_IC_NUM; i ++)
	{ 
		if(!IC_Buffer[i].used)
	 	{
			memset(IC_Buffer + i, 0, sizeof(*IC_Buffer));
			IC_Buffer[i].used = 1;
			IC_Buffer[i].id = (++__ic_counter__);
			strncpy(IC_Buffer[i].TrayIcon, default_tray_icon, 256);
			memcpy(&IC_Buffer[i].mutex, &mutex_init, sizeof(pthread_mutex_t));
			IC_Buffer[i].CreatorThread = pthread_self();
			//TODO init other buffers
			return IC_Buffer + i;
		}
	}
	return NULL;
}
void ICDestory(InputContext* ic)
{
	if(NULL == ic) return;
	CHK_CREATOR(ic, ); /* Prevent thread other than creator Destory the IC */
	if(ic->Enabled) FreeIMIC(ic);
	ic->used = -1;
}
InputContext* FindIC(CARD16 id)
{
	int i;
	if(0 == id) //because ICID should not be set to 0, so if 0 is passed, create new one
	{
		return ICNew();
	}
	for(i = 0; i < MAX_IC_NUM; i ++)
	{
		if(IC_Buffer[i].id == id && IC_Buffer[i].used)
		{
			return IC_Buffer + i;
		}
	}
	return NULL;
}
int ModifyIC(IMChangeICStruct* data)
{
	InputContext* ic = FindIC(data->icid);
	if(NULL == ic)
	{
		VINE_LOG_WARNING("I can't find IC %d",data->icid);
		return -1;
	}
	LockIC(ic);
	data->icid = ic->id; //make sure that ICID matches
	int i;
	XICAttribute* p;
#define INIT_ATTR() if(0)
#define END_ATTR()  else VINE_LOG_NOTICE("Unknown attribute %s", p->name)
#define CASE_ATTR(attr,path) else if (STREQU(XN##attr,p->name)) 
#define SAVE_ATTR(attr,path) CASE_ATTR(attr,path) ({\
		VINE_LOG_DEBUG("saved attribute %s%s",#path,p->name);\
		ic->path attr = *(typeof(ic->path attr)*) p->value;\
		})
#define SAVE_I_ATTR(attr) SAVE_ATTR(attr,)
#define SAVE_P_ATTR(attr) SAVE_ATTR(attr,pattr.)
#define SAVE_S_ATTR(attr) SAVE_ATTR(attr,sattr.)
	for(i = 0, p = data->ic_attr; i < data->ic_attr_num; i ++, p++)
	{
		INIT_ATTR();
		SAVE_I_ATTR(InputStyle);
		SAVE_I_ATTR(ClientWindow);
		SAVE_I_ATTR(FocusWindow);
		END_ATTR();
	}
	for(i = 0, p = data->preedit_attr; i< data->preedit_attr_num; i++, p++)
	{
		INIT_ATTR();
		SAVE_P_ATTR(Area);
		SAVE_P_ATTR(AreaNeeded);
		SAVE_P_ATTR(SpotLocation);
		SAVE_P_ATTR(StdColormap);
		SAVE_P_ATTR(Foreground);
		SAVE_P_ATTR(Background);
		SAVE_P_ATTR(BackgroundPixmap);
		CASE_ATTR(FontSet,pattr.){
			strncpy(ic->pattr.FontSet, p->value, sizeof(ic->pattr.FontSet));
		}
		SAVE_P_ATTR(LineSpace);
		SAVE_P_ATTR(Cursor);
		END_ATTR();
	}
	for(i = 0, p = data->status_attr; i< data->status_attr_num; i++, p++)
	{
		INIT_ATTR();
		SAVE_S_ATTR(Area);
		SAVE_S_ATTR(AreaNeeded);
		SAVE_S_ATTR(StdColormap);
		SAVE_S_ATTR(BackgroundPixmap);
		CASE_ATTR(FontSet,sattr.){
			strncpy(ic->sattr.FontSet, p->value, sizeof(ic->sattr.FontSet));
		}
		SAVE_S_ATTR(LineSpace);
		SAVE_S_ATTR(Cursor);
		SAVE_S_ATTR(Background);
		SAVE_S_ATTR(Foreground);
		END_ATTR();
	}
	UnlockIC(ic);
	if(ic->IMWindowVisable)
	{
		FollowCursorPosition(ic);
	}
	return 0;
}
int QueryIC(IMChangeICStruct* data)
{
	InputContext* ic = GetCurrentIC(data);
	if(NULL == ic)
	{
		VINE_LOG_ERROR("Bad IC(%d)",data->icid);
		return -1;
	}
	LockIC(ic);
#define GET_ATTR(attr,path) CASE_ATTR(attr,path) ({\
		VINE_LOG_DEBUG("get attribute %s",#attr);\
		p->value_length = sizeof(ic->path attr);\
	        p->value	= malloc(p->value_length);\
		*(typeof(&ic->path attr))p->value = ic->path attr;\
		})
#define GET_P_ATTR(attr) GET_ATTR(attr,pattr.)
#define GET_S_ATTR(attr) GET_ATTR(attr,sattr.)
	int i;
	XICAttribute* p;
	for(i = 0, p = data->ic_attr; i < data->ic_attr_num; i ++, p ++)
	{
		INIT_ATTR();
		CASE_ATTR(FilterEvents,){
			p->value_length = sizeof(CARD32);
			p->value = malloc(sizeof(CARD32));
			*(CARD32*) p->value = KeyPressMask | KeyRelease;
		}
		END_ATTR();
	}
	for(i = 0, p = data->preedit_attr; i < data->preedit_attr_num; i ++, p ++)
	{
		INIT_ATTR();
		GET_P_ATTR(Area);
		GET_P_ATTR(AreaNeeded);
		GET_P_ATTR(SpotLocation);
		GET_P_ATTR(StdColormap);
		GET_P_ATTR(Foreground);
		GET_P_ATTR(BackgroundPixmap);
		GET_P_ATTR(BackgroundPixmap);
		GET_P_ATTR(LineSpace);
		GET_P_ATTR(Cursor);
		CASE_ATTR(FontSet,pattr.){
			size_t len = strlen(ic->pattr.FontSet);
			p->value_length = len + 1;
			p->value = malloc(len + 1);
			strncpy(p->value, ic->pattr.FontSet, len + 1);
		}
		END_ATTR();	
	}
	for(i = 0, p = data->status_attr; i < data->status_attr_num; i ++, p ++)
	{
		INIT_ATTR();
		GET_S_ATTR(Area);
		GET_S_ATTR(AreaNeeded);
		GET_S_ATTR(StdColormap);
		GET_S_ATTR(Foreground);
		GET_S_ATTR(Background);
		GET_S_ATTR(BackgroundPixmap);
		GET_S_ATTR(LineSpace);
		GET_S_ATTR(Cursor);
		CASE_ATTR(FontSet,sattr.){
			size_t len = strlen(ic->sattr.FontSet);
			p->value_length = len + 1;
			p->value = malloc(len + 1);
			strncpy(p->value, ic->sattr.FontSet, len + 1);
		}
		END_ATTR();
	}
	UnlockIC(ic);
	return 0;
}
InputContext* GetCurrentIC(void* data)
{
	CARD16 icid = ((ICID*)data)->icid;
	if(NULL == current_input_context || icid != current_input_context->id)
	{
		current_input_context = NULL;
		if(icid) current_input_context = FindIC(icid);	
	}
	return current_input_context;
}
InputContext* GetCurrentICByID(CARD16 id)
{
	CARD16 icid = id;
	if(NULL == current_input_context || icid != current_input_context->id)
	{
		current_input_context = NULL;
		if(icid) current_input_context = FindIC(icid);	
	}
	if(NULL == current_input_context)
	{
		VINE_LOG_WARNING("no such input context %u",icid);
	}
	return current_input_context;
}
int UpdateCandidates(InputContext* ic, char** candidates, size_t n)
{
	if(NULL == ic) return -1;
	if(ic->Candidates.Capacity < n)
	{
		VINE_LOG_WARNING("IM Returns too much candidates, truncked");
		n = ic->Candidates.Capacity;
	}
	LockIC(ic);
	ic->Candidates.Count = n;
	int i;
	for(i = 0; i < n; i ++)
		ic->Candidates.Candidate[i] = candidates[i];
	UnlockIC(ic);
	//TODO: update UI
	if(!ic->IMWindowVisable)
	{
		ic->IMWindowVisable = 1;
		ShowIMWindow();
		FollowCursorPosition(ic);
	}
	UpdateIMWindow(NULL, ic->Candidates.Candidate, ic->Candidates.Count);
	return 0;
}
int UpdateCommit(InputContext* ic, char* text)
{
	if(NULL == ic) return -1;
	LockIC(ic);
	if(text != ic->PrecommitBuffer) strncpy(ic->PrecommitBuffer, text, PREEDIT_BUF);
	UnlockIC(ic);
	//TODO: update UI
	if(!ic->IMWindowVisable)
	{
		ic->IMWindowVisable = 1;
		ShowIMWindow();
		FollowCursorPosition(ic);
	}
	UpdateIMWindow(ic->PrecommitBuffer,NULL,0);
	return 0;
}
int FreeIMIC(InputContext* ic)
{
	if(NULL == ic->CurrentIM) return 0;
	CHK_CREATOR(ic,-1);
	int ret = 0;
        if(ic->IMIC) 
	{
		ret = IME_CALL(ic->CurrentIM,OnDisabled,ic->IMIC);
		free((char*)ic->IMIC - sizeof(IMIC)); //Free current IMIC
	}
	ic->CurrentIM = NULL;
	ic->IMIC = NULL;
	if(ic->Enabled == 0)
	{//IC Disable, Reset to default icon
		strncpy(ic->TrayIcon, default_tray_icon, 256);
		RedrawIC(ic);
	}
	if(ic->IMWindowVisable)
	{
		HideIMWindow();
		ic->IMWindowVisable = 0;
	}
	return ret;
}
int CreateIMIC(InputContext* ic, IMMetaData* im)
{
	if(NULL == ic && NULL == im) return -1;
	CHK_CREATOR(ic,-1);
	if(ic->CurrentIM == im)
	{
		if(ic->IMIC) //if there are an IMIC, does not need to create one
			return 0;
	}
	else //If There's an opened IMIC for other IM, close it first
		FreeIMIC(ic);
	ic->CurrentIM = im;
	IMIC* imic = (IMIC*)malloc(im->IC_Size + sizeof(IMIC));
	memset(imic, 0, sizeof(IMIC) + im->IC_Size);
	if(NULL == imic)
	{
		VINE_LOG_ERROR("Can not create IMIC for %s(param = %s)",im->Name,im->Parameter);
		return -1;
	}
	ic->IMIC = (void*)imic->data;
	imic->IC = ic;
	ic->ForwardFilter[0].proc = NULL;  //Reset ForwardKeyFilter
	if(IME_CALL(im,OnEnabled,ic->IMIC) < 0)
	{
		VINE_LOG_ERROR("Failed to enable IM %s(param = %s)",im->Name, im->Parameter);
		free(imic);
		ic->IMIC = NULL;
		return -1;
	}
	ic->Candidates.Capacity = ((im->NCandidates>MAX_CAND)?MAX_CAND:im->NCandidates);
	VINE_LOG_NOTICE("Input Method Context %s(param = %s) Created", im->Name, im->Parameter);
	return 0;
}
int SetForwardFilter(InputContext* ic, uint32_t sym_left, uint32_t sym_right,
		     uint32_t mask_pos, uint32_t mask_neg, uint32_t release,
		     KeyForwardProc proc)
{
	int i;
	if(sym_right <= sym_left || (mask_pos & mask_neg)) return 0; //invalid filter, ignore
	LockIC(ic);
	for(i = 0; i < MAX_FORWARD_KEYS && ic->ForwardFilter[i].proc ; i ++);
	if(MAX_FORWARD_KEYS == i)
	{
		VINE_LOG_WARNING("set forward filter failed : too many forward rules");
		return -1;
	}
	ic->ForwardFilter[i].proc = proc;
	ic->ForwardFilter[i].sym_left  = sym_left;
	ic->ForwardFilter[i].sym_right = sym_right;
	ic->ForwardFilter[i].smask_neg = mask_neg;
	ic->ForwardFilter[i].smask_pos = mask_pos;
	ic->ForwardFilter[i].release   = release;
	ic->ForwardFilter[i].enabled = 1;
	ic->ForwardFilter[i+1].proc = NULL;
	UnlockIC(ic);
	VINE_LOG_DEBUG("Forward Filter [%u,%u) neg=0x%x pos=0x%x rel=%u",sym_left,
			sym_right, mask_neg, mask_pos, release);
        return i;	
}
KeyForwardProc GetForwardProc(InputContext* ic, uint32_t sym, uint32_t state, uint32_t release,uint32_t* id)
{
	int i;
	ForwardEvent* p;
	LockIC(ic);
	for(i = 0, p = ic->ForwardFilter; i < MAX_FORWARD_KEYS && p->proc; i ++, p++)
	{
		if(!p->enabled) continue;
		if(p->release != IMKeyActionAny && p->release != release) continue;
		if(p->sym_left <= sym && sym < p->sym_right &&
		   (state & p->smask_neg) == 0 &&
		   (state & p->smask_pos) == p->smask_pos)
		{
			if(id) *id = i;
			KeyForwardProc ret = p->proc;
			UnlockIC(ic);
			return ret;
		}
	}
	UnlockIC(ic);
	return NULL;
}	
int SwitchForwardFilter(InputContext* ic, int index, int enable)
{
	if(index < 0 || index >= MAX_FORWARD_KEYS) return -1;
	if(NULL == ic) return -1;
	LockIC(ic);
	ic->ForwardFilter[index].enabled = enable;
	UnlockIC(ic);
	return 0;
}
int ResetForwardFilter(InputContext* ic)
{
	if(NULL == ic) return -1;
	LockIC(ic);
	ic->ForwardFilter[0].proc = NULL;
	UnlockIC(ic);
	return 0;
}
int CommitText(InputContext* ic,char* text)
{
	XTextProperty tp;
	IMCommitStruct mc;
	Xutf8TextListToTextProperty(display, &text, 1, XCompoundTextStyle, &tp);
	memset(&mc,0,sizeof(mc));
	LockIC(ic);
	mc.icid = ic->id;
	mc.connect_id = ic->curent_connect;
	UnlockIC(ic);
        mc.flag = XLookupChars;
	mc.commit_string = (char*)tp.value;
	IMCommitString(xims, (XPointer) &mc);
	XFree(tp.value);
	if(ic->IMWindowVisable)
	{
		HideIMWindow();
		ic->IMWindowVisable = 0;
	}
	return 0;
}
int RedrawIC(InputContext* ic)
{
	SetTrayIcon(ic->TrayIcon);
	if(ic->Enabled == 0 || !ic->CurrentIM)
	{
		SetTrayToolTip("Vine(Disabled)");
	}
	else
	{
		char buf[128];
		snprintf(buf,128,"Vine(%s)",ic->CurrentIM->Name);
		SetTrayToolTip(buf);
	}
	return 1;
}
int FollowCursorPosition(InputContext* ic)
{
	int x,y;
	Window child;
	XTranslateCoordinates(display, ic->ClientWindow, rootwindow,
			      ic->pattr.SpotLocation.x,ic->pattr.SpotLocation.y,
			      &x,&y,&child);
	XMoveWindow(display,imxwin,x,y);
	return 0;
}
void FinalizeIC()
{
	int i;
	for(i = 0; i < MAX_IC_NUM; i ++)
	{
		if(IC_Buffer[i].IMIC) FreeIMIC(IC_Buffer + i);
	}
}
int LockIC(InputContext* ic)
{
	if(NULL == ic) return -1;
	pthread_mutex_lock(&(ic->mutex));
	return 0;
}
int UnlockIC(InputContext* ic)
{
	if(NULL == ic) return -1;
	pthread_mutex_unlock(&(ic->mutex));
	return 0;
}
